2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
24 #import <XCTest/XCTest.h>
25 #import "keychain/Signin Metrics/SFSignInAnalytics.h"
26 #import "keychain/Signin Metrics/SFSignInAnalytics+Internal.h"
27 #import "keychain/ot/OTDefines.h"
28 #import "SFAnalytics+Signin.h"
29 #import <Security/SecureObjectSync/SOSCloudCircle.h>
31 static NSInteger _testnum;
32 static NSString* _path;
34 @interface SFSignInAnalyticsTester : SFSignInAnalytics
38 @implementation SFSignInAnalyticsTester
40 + (NSString*)databasePath {
46 self = [super initWithSignInUUID:[NSUUID UUID].UUIDString category:@"CoreCDP" eventName:@"signin"];
54 @interface SignInAnalyticsTests : XCTestCase
55 @property (nonatomic) SFSignInAnalyticsTester *metric;
58 @implementation SignInAnalyticsTests
64 self.continueAfterFailure = NO;
65 _path = [@"/tmp" stringByAppendingFormat:@"/test_%ld.db", (long)++_testnum];
66 _metric = [[SFSignInAnalyticsTester alloc] init];
67 XCTAssertNotNil(_metric, "SignInAnalyticsTester object should not be nil");
72 dispatch_async([SFSIALoggerObject logger].queue, ^{
73 [[SFSIALoggerObject logger].database executeSQL:@"delete from all_events"];
74 [[SFSIALoggerObject logger].database executeSQL:@"delete from soft_failures"];
75 [[SFSIALoggerObject logger].database executeSQL:@"delete from hard_failures"];
78 [[SFSIALoggerObject logger] removeState];
86 NSDictionary *attributes = @{@"success": @YES,
87 @"takenFlow" : @"restore",
89 XCTAssertNotNil(attributes, "attributes dictionary should exist");
91 [_metric stopWithAttributes:attributes];
93 NSArray* results = [[SFSIALoggerObject logger].database allEvents];
95 XCTAssertEqual([results count], 2, @"should have 2 results");
106 NSError* error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorBottleID userInfo:@{NSLocalizedDescriptionKey: @"Failed to deserialize bottle peer"}];
107 [_metric logRecoverableError:error];
108 NSArray* results = [[SFSIALoggerObject logger].database softFailures];
109 XCTAssertEqual([results count], 1, @"should have 1 results");
112 - (void)testCreateNewSubtask
114 SFSignInAnalytics* child = [_metric newSubTaskForEvent:@"restore"];
115 XCTAssertNotNil(child, "child should be created");
116 [[SFSIALoggerObject logger] removeState];
121 - (void)testCreateNewSubtaskAndStop
123 SFSignInAnalytics* child = [_metric newSubTaskForEvent:@"restore"];
126 NSDictionary *attributes = @{@"success": @YES,
127 @"takenFlow" : @"piggyback",
130 [child stopWithAttributes:attributes];
132 XCTAssertNotNil(child, "child should be created");
134 NSArray* results = [[SFSIALoggerObject logger].database allEvents];
136 XCTAssertEqual([results count], 2, @"should have 2 results");
138 [[SFSIALoggerObject logger] removeState];
142 - (void)testStopAfterCancel
145 NSDictionary *attributes = @{@"success": @YES,
146 @"takenFlow" : @"piggyback",
148 XCTAssertNotNil(attributes, "attributes dictionary should exist");
152 [_metric stopWithAttributes:attributes];
154 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
156 XCTAssertEqual([allEvents count], 0, @"should have 0 things logged");
159 - (void)testStopAfterStop
162 NSDictionary *attributes = @{@"success": @YES,
163 @"takenFlow" : @"piggyback",
165 XCTAssertNotNil(attributes, "attributes dictionary should exist");
167 [_metric stopWithAttributes:attributes];
169 [_metric stopWithAttributes:attributes];
171 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
173 XCTAssertEqual([allEvents count], 2, @"should have 2 things logged");
176 -(void)testSignInComplete
178 NSDictionary* attributes = [NSDictionary dictionary];
179 [_metric stopWithAttributes:attributes];
181 [_metric signInCompleted];
183 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
184 XCTAssertNotNil(allEvents, "array should not be nil");
185 XCTAssertTrue(allEvents && [allEvents count] > 0, "array should not be nil and contain an entry");
187 NSDictionary *dependencyEntry = [allEvents objectAtIndex:[allEvents count]-1];
188 XCTAssertTrue(dependencyEntry && [dependencyEntry count] > 0, "dictionary should not be nil and contain an entry");
190 NSArray *chains = [dependencyEntry objectForKey:@"dependencyChains"];
192 XCTAssertEqual([chains count], 1, "should be one list");
194 XCTAssertTrue([chains containsObject:_metric.signin_uuid], "should contain 1 uuid");
197 -(void)testSingleChainDependencyList
199 SFSignInAnalytics* child1 = [_metric newSubTaskForEvent:@"piggyback"];
200 XCTAssertNotNil(child1, "child1 should be created");
202 SFSignInAnalytics* child2 = [child1 newSubTaskForEvent:@"initialsync"];
203 XCTAssertNotNil(child2, "child2 should be created");
205 SFSignInAnalytics* child3 = [child2 newSubTaskForEvent:@"backup"];
206 XCTAssertNotNil(child3, "child3 should be created");
208 SFSignInAnalytics* child4 = [child3 newSubTaskForEvent:@"processing one ring"];
209 XCTAssertNotNil(child4, "child4 should be created");
211 [_metric signInCompleted];
213 NSString *expectedChain = [NSString stringWithFormat:@"%@, %@, %@, %@, %@", child1.signin_uuid, child1.my_uuid, child2.my_uuid, child3.my_uuid, child4.my_uuid];
215 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
216 XCTAssertNotNil(allEvents, "should not be nil");
217 XCTAssertTrue([allEvents count] > 0, "should be events");
219 NSDictionary *dependencyEntry = [allEvents objectAtIndex:[allEvents count]-1];
220 NSArray *chains = [dependencyEntry objectForKey:@"dependencyChains"];
222 XCTAssertEqual([chains count], 1, "should be one list");
223 XCTAssertTrue([expectedChain isEqualToString:[chains objectAtIndex:0]], "chains should be the same");
236 -(void)testMultipleChildrenPerEvent
238 SFSignInAnalytics* child1 = [_metric newSubTaskForEvent:@"piggyback"];
239 XCTAssertNotNil(child1, "child1 should be created");
241 SFSignInAnalytics* child2 = [child1 newSubTaskForEvent:@"initialsync"];
242 XCTAssertNotNil(child2, "child2 should be created");
244 SFSignInAnalytics* child3 = [child1 newSubTaskForEvent:@"backup"];
245 XCTAssertNotNil(child3, "child3 should be created");
247 SFSignInAnalytics* child4 = [child1 newSubTaskForEvent:@"processing one ring"];
248 XCTAssertNotNil(child4, "child4 should be created");
250 SFSignInAnalytics* child5 = [child2 newSubTaskForEvent:@"processing second ring"];
251 XCTAssertNotNil(child5, "child5 should be created");
253 SFSignInAnalytics* child6 = [child2 newSubTaskForEvent:@"processing third ring"];
254 XCTAssertNotNil(child6, "child6 should be created");
256 SFSignInAnalytics* child7 = [child2 newSubTaskForEvent:@"processing fourth ring"];
257 XCTAssertNotNil(child7, "child7 should be created");
259 SFSignInAnalytics* child8 = [child7 newSubTaskForEvent:@"processing fifth ring"];
260 XCTAssertNotNil(child8, "child8 should be created");
262 SFSignInAnalytics* child9 = [child7 newSubTaskForEvent:@"processing one ring"];
263 XCTAssertNotNil(child9, "child9 should be created");
265 NSString *expectedChain = [NSString stringWithFormat:@"%@, %@, %@, %@", _metric.signin_uuid, child1.my_uuid, child2.my_uuid, child5.my_uuid];
267 NSString *expectedChain1 = [NSString stringWithFormat:@"%@, %@, %@", _metric.signin_uuid, child1.my_uuid, child3.my_uuid];
269 NSString *expectedChain2 = [NSString stringWithFormat:@"%@, %@, %@", _metric.signin_uuid, child1.my_uuid, child4.my_uuid];
271 NSString *expectedChain3 = [NSString stringWithFormat:@"%@, %@, %@, %@", _metric.signin_uuid, child1.my_uuid, child2.my_uuid, child5.my_uuid];
273 NSString *expectedChain4 = [NSString stringWithFormat:@"%@, %@, %@, %@, %@", _metric.signin_uuid, child1.my_uuid, child2.my_uuid, child7.my_uuid, child8.my_uuid];
275 NSString *expectedChain5 = [NSString stringWithFormat:@"%@, %@, %@, %@, %@", _metric.signin_uuid, child1.my_uuid, child2.my_uuid, child7.my_uuid, child9.my_uuid];
278 [_metric signInCompleted];
280 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
281 XCTAssertNotNil(allEvents, "array is not nil");
282 XCTAssertTrue([allEvents count] > 0, "array should not be empty");
284 NSDictionary *dependencyEntry = [allEvents objectAtIndex:[allEvents count]-1];
285 NSArray *chains = [dependencyEntry objectForKey:@"dependencyChains"];
287 XCTAssertEqual([chains count], 6, "should be one list");
289 XCTAssertTrue([chains containsObject:expectedChain], "chains should contain expectedChain");
290 XCTAssertTrue([chains containsObject:expectedChain1], "chains should contain expectedChain1");
291 XCTAssertTrue([chains containsObject:expectedChain2], "chains should contain expectedChain2");
292 XCTAssertTrue([chains containsObject:expectedChain3], "chains should contain expectedChain3");
293 XCTAssertTrue([chains containsObject:expectedChain4], "chains should contain expectedChain4");
294 XCTAssertTrue([chains containsObject:expectedChain5], "chains should contain expectedChain5");
296 [[SFSIALoggerObject logger] removeState];
310 -(void)testSOSCCWaitForInitialSync
312 CFErrorRef error = nil;
313 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
314 XCTAssertNotNil(archiver, "should not be nil");
315 [_metric encodeWithCoder:archiver];
316 CFDataRef parentData = (__bridge CFDataRef)archiver.encodedData;
317 XCTAssertNotNil((__bridge NSData*)parentData, "should not be nil");
318 bool worked = SOSCCWaitForInitialSyncWithAnalytics(parentData, &error);
319 XCTAssertTrue(worked, "should have worked");
321 [_metric signInCompleted];
323 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
324 XCTAssertNotNil(allEvents, "array is not nil");
325 XCTAssertEqual([allEvents count], 1, "array should not contain 1 entry");
327 NSDictionary *dependencyEntry = [allEvents objectAtIndex:[allEvents count]-1];
328 NSArray *chains = [dependencyEntry objectForKey:@"dependencyChains"];
329 XCTAssertNotNil(chains, "chains is not nil");
330 XCTAssertEqual([chains count], 1, "array should not contain 1 entry");
333 -(void)testSOSCCRemoveThisDeviceFromCircle
335 CFErrorRef error = nil;
336 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
337 XCTAssertNotNil(archiver, "should not be nil");
338 [_metric encodeWithCoder:archiver];
339 CFDataRef parentData = (__bridge CFDataRef)archiver.encodedData;
340 XCTAssertNotNil((__bridge NSData*)parentData, "should not be nil");
341 bool worked = SOSCCRemoveThisDeviceFromCircleWithAnalytics(parentData, &error);
342 XCTAssertTrue(worked, "should have worked");
344 [_metric signInCompleted];
346 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
347 XCTAssertNotNil(allEvents, "array is not nil");
348 XCTAssertEqual([allEvents count], 1, "array should not contain 1 entry");
350 NSDictionary *dependencyEntry = [allEvents objectAtIndex:[allEvents count]-1];
351 NSArray *chains = [dependencyEntry objectForKey:@"dependencyChains"];
352 XCTAssertNotNil(chains, "chains is not nil");
353 XCTAssertEqual([chains count], 1, "array should not contain 1 entry");
356 -(void)testSOSCCRequestToJoinCircle
358 CFErrorRef error = nil;
359 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
360 XCTAssertNotNil(archiver, "should not be nil");
361 [_metric encodeWithCoder:archiver];
362 CFDataRef parentData = (__bridge CFDataRef)archiver.encodedData;
363 XCTAssertNotNil((__bridge NSData*)parentData, "should not be nil");
364 SOSCCRequestToJoinCircleWithAnalytics(parentData, &error);
366 [_metric signInCompleted];
368 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
369 XCTAssertNotNil(allEvents, "array is not nil");
370 XCTAssertEqual([allEvents count], 1, "array should not contain 1 entry");
372 NSDictionary *dependencyEntry = [allEvents objectAtIndex:[allEvents count]-1];
373 NSArray *chains = [dependencyEntry objectForKey:@"dependencyChains"];
374 XCTAssertNotNil(chains, "chains is not nil");
375 XCTAssertEqual([chains count], 1, "array should not contain 1 entry");
378 -(void)testSOSCCRequestToJoinCircleAfterRestore
380 CFErrorRef error = nil;
381 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
382 XCTAssertNotNil(archiver, "should not be nil");
383 [_metric encodeWithCoder:archiver];
384 CFDataRef parentData = (__bridge CFDataRef)archiver.encodedData;
385 XCTAssertNotNil((__bridge NSData*)parentData, "should not be nil");
386 SOSCCRequestToJoinCircleAfterRestoreWithAnalytics(parentData, &error);
388 [_metric signInCompleted];
390 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
391 XCTAssertNotNil(allEvents, "array is not nil");
392 XCTAssertEqual([allEvents count], 1, "array should not contain 1 entry");
394 NSDictionary *dependencyEntry = [allEvents objectAtIndex:[allEvents count]-1];
395 NSArray *chains = [dependencyEntry objectForKey:@"dependencyChains"];
396 XCTAssertNotNil(chains, "chains is not nil");
397 XCTAssertEqual([chains count], 1, "array should not contain 1 entry");
400 -(void)testSOSCCRemovePeersFromCircle
402 CFErrorRef error = nil;
403 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
404 XCTAssertNotNil(archiver, "should not be nil");
405 [_metric encodeWithCoder:archiver];
406 CFDataRef parentData = (__bridge CFDataRef)archiver.encodedData;
407 XCTAssertNotNil((__bridge NSData*)parentData, "should not be nil");
408 NSArray* peers = [NSArray array];
409 SOSCCRemovePeersFromCircleWithAnalytics((__bridge CFArrayRef)peers, parentData, &error);
411 [_metric signInCompleted];
413 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
414 XCTAssertNotNil(allEvents, "array is not nil");
415 XCTAssertEqual([allEvents count], 1, "array should not contain 1 entry");
417 NSDictionary *dependencyEntry = [allEvents objectAtIndex:[allEvents count]-1];
418 NSArray *chains = [dependencyEntry objectForKey:@"dependencyChains"];
419 XCTAssertNotNil(chains, "chains is not nil");
420 XCTAssertEqual([chains count], 1, "array should not contain 1 entry");
424 -(void)testSOSCCViewSet
426 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
427 XCTAssertNotNil(archiver, "should not be nil");
428 [_metric encodeWithCoder:archiver];
429 CFDataRef parentData = (__bridge CFDataRef)archiver.encodedData;
430 XCTAssertNotNil((__bridge NSData*)parentData, "should not be nil");
431 CFSetRef enabledViews = nil;
432 CFSetRef disabledViews = nil;
433 SOSCCViewSetWithAnalytics(enabledViews, disabledViews, parentData);
435 [_metric signInCompleted];
437 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
438 XCTAssertNotNil(allEvents, "array is not nil");
439 XCTAssertEqual([allEvents count], 1, "array should not contain 1 entry");
441 NSDictionary *dependencyEntry = [allEvents objectAtIndex:[allEvents count]-1];
442 NSArray *chains = [dependencyEntry objectForKey:@"dependencyChains"];
443 XCTAssertNotNil(chains, "chains is not nil");
444 XCTAssertEqual([chains count], 1, "array should not contain 1 entry");
447 -(void)testSOSCCSetUserCredentialsAndDSID
449 CFErrorRef error = nil;
450 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
451 XCTAssertNotNil(archiver, "should not be nil");
452 [_metric encodeWithCoder:archiver];
453 CFDataRef parentData = (__bridge CFDataRef)archiver.encodedData;
454 XCTAssertNotNil((__bridge NSData*)parentData, "should not be nil");
455 CFStringRef label = nil;
456 CFDataRef password = nil;
457 CFStringRef dsid = nil;
458 SOSCCSetUserCredentialsAndDSIDWithAnalytics(label, password, dsid, parentData, &error);
460 [_metric signInCompleted];
462 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
463 XCTAssertNotNil(allEvents, "array is not nil");
464 XCTAssertEqual([allEvents count], 1, "array should not contain 1 entry");
466 NSDictionary *dependencyEntry = [allEvents objectAtIndex:[allEvents count]-1];
467 NSArray *chains = [dependencyEntry objectForKey:@"dependencyChains"];
468 XCTAssertNotNil(chains, "chains is not nil");
469 XCTAssertEqual([chains count], 1, "array should not contain 1 entry");
472 -(void)testSOSCCResetToEmpty
474 CFErrorRef error = nil;
475 NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initRequiringSecureCoding:YES];
476 XCTAssertNotNil(archiver, "should not be nil");
477 [_metric encodeWithCoder:archiver];
478 CFDataRef parentData = (__bridge CFDataRef)archiver.encodedData;
479 XCTAssertNotNil((__bridge NSData*)parentData, "should not be nil");
480 SOSCCResetToEmptyWithAnalytics(parentData, &error);
482 [_metric signInCompleted];
484 NSArray *allEvents = [[SFSIALoggerObject logger].database allEvents];
485 XCTAssertNotNil(allEvents, "array is not nil");
486 XCTAssertEqual([allEvents count], 1, "array should not contain 1 entry");
488 NSDictionary *dependencyEntry = [allEvents objectAtIndex:[allEvents count]-1];
489 NSArray *chains = [dependencyEntry objectForKey:@"dependencyChains"];
490 XCTAssertNotNil(chains, "chains is not nil");
491 XCTAssertEqual([chains count], 1, "array should not contain 1 entry");
494 - (void)testMultipleDBConnections
496 NSError* error = [NSError errorWithDomain:OctagonErrorDomain code:OTErrorBottleID userInfo:@{NSLocalizedDescriptionKey: @"Failed to deserialize bottle peer"}];
497 dispatch_queue_t test_queue = dispatch_queue_create("com.apple.security.signin.tests", DISPATCH_QUEUE_CONCURRENT_WITH_AUTORELEASE_POOL);
499 for(int i = 0; i < 1000; i++){
500 SFSignInAnalytics *test1 = [_metric newSubTaskForEvent:@"connection1"];
501 SFSignInAnalytics *test2 = [_metric newSubTaskForEvent:@"connection2"];
503 dispatch_async(test_queue, ^{
504 [test1 logRecoverableError:error];
506 dispatch_async(test_queue, ^{
507 [test2 stopWithAttributes:nil];
509 dispatch_async(test_queue, ^{
510 [self->_metric logRecoverableError:error];